Android 刘海屏及9.0适配

前言

在分享适配刘海屏之前,先吐槽一波苹果。居然搞了个畸形屏幕,如果乔大爷还在,这种产品还能问世吗?

1.解决方案思路

通过上图我们不难看出,在刘海屏/圆角屏手机,为保证UI视图能完整展示,我们应通过计算,获取到可用区域的宽高(蓝色区域),并且尽量避免我们的view/window超出此区域,特别注意交互事件。这里有个点要注意:在Android P之后,状态栏区域如果要显示window,必须在Mainfest中添加权限。

2.Android P之前的刘海屏适配

在P之前,国产厂商在Android P之前(基本都是Android O)就用上了高档大气上档次的刘海屏,所以,这也造就了各大厂商在Android P之前的解决方案百花齐放。下面,我们来看下主流厂商:华为、vivo、OPPO、小米等所提供的方案。

各手机厂商官方文档:
OPPO:https://open.oppomobile.com/wiki/doc#id=10159
VIVO:https://dev.vivo.com.cn/doc/document/info?id=103
HUAWEI:https://mini.eastday.com/bdmip/180411011257629.html

这里说挖孔屏是状态栏高度的两倍
https://arstechnica.com/gadgets/2017/09/essential-phone-review-impressive-for-a-new-company-but-not-competitive/
然而不同厂商的挖孔高度明显参差不齐,所以使用获取状态栏高度来决定刘海高度这种方案是不可行的。

2.1 华为

2.1.1 使用刘海区显示

使用新增的meta-data属性android.notch_support。在应用的AndroidManifest.xml中增加meta-data属性,此属性不仅可以针对Application生效,也可以对Activity配置生效。如下所示:


复制代码

• 对Application生效,意味着该应用的所有页面,系统都不会做竖屏场景的特殊下移或者是横屏场景的右移特殊处理。
• 对Activity生效,意味着可以针对单个页面进行刘海屏适配,设置了该属性的Activity系统将不会做特殊处理。

2.1.2 是否有刘海屏

通过以下代码即可知道华为手机上是否有刘海屏了,true为有刘海,false则没有。

public static boolean hasNotchAtHuawei(Context context) {
    boolean ret = false;
    try {
        ClassLoader classLoader = context.getClassLoader();
        Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
        Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
        ret = (boolean) get.invoke(HwNotchSizeUtil);
    } catch (ClassNotFoundException e) {
        Log.e("Notch", "hasNotchAtHuawei ClassNotFoundException");
    } catch (NoSuchMethodException e) {
        Log.e("Notch", "hasNotchAtHuawei NoSuchMethodException");
    } catch (Exception e) {
        Log.e("Notch", "hasNotchAtHuawei Exception");
    } finally {
        return ret;
    }
}

2.1.3 刘海尺寸

华为提供了接口获取刘海的尺寸,如下:

//获取刘海尺寸:width、height
//int[0]值为刘海宽度 int[1]值为刘海高度
public static int[] getNotchSizeAtHuawei(Context context) {
    int[] ret = new int[]{0, 0};
    try {
        ClassLoader cl = context.getClassLoader();
        Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
        Method get = HwNotchSizeUtil.getMethod("getNotchSize");
        ret = (int[]) get.invoke(HwNotchSizeUtil);
    } catch (ClassNotFoundException e) {
        Log.e("Notch", "getNotchSizeAtHuawei ClassNotFoundException");
    } catch (NoSuchMethodException e) {
        Log.e("Notch", "getNotchSizeAtHuawei NoSuchMethodException");
    } catch (Exception e) {
        Log.e("Notch", "getNotchSizeAtHuawei Exception");
    } finally {
        return ret;
    }
}

2.2 vivo

vivo在设置–显示与亮度–第三方应用显示比例中可以切换是否全屏显示还是安全区域显示。

2.2.1 是否有刘海屏

public static final int VIVO_NOTCH = 0x00000020;//是否有刘海
public static final int VIVO_FILLET = 0x00000008;//是否有圆角

public static boolean hasNotchAtVoio(Context context) {
    boolean ret = false;
    try {
        ClassLoader classLoader = context.getClassLoader();
        Class FtFeature = classLoader.loadClass("android.util.FtFeature");
        Method method = FtFeature.getMethod("isFeatureSupport", int.class);
        ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
    } catch (ClassNotFoundException e) {
        Log.e("Notch", "hasNotchAtVoio ClassNotFoundException");
    } catch (NoSuchMethodException e) {
        Log.e("Notch", "hasNotchAtVoio NoSuchMethodException");
    } catch (Exception e) {
        Log.e("Notch", "hasNotchAtVoio Exception");
    } finally {
        return ret;
    }
}

2.2.2 刘海尺寸

vivo不提供接口获取刘海尺寸,目前vivo的刘海宽为100dp,高为27dp。

2.3 OPPO

OPPO目前在设置 – 显示 – 应用全屏显示 – 凹形区域显示控制,里面有关闭凹形区域开关。

2.3.1 是否有刘海屏

public static boolean hasNotchInScreenAtOPPO(Context context) {
    return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}

2.3.2 刘海尺寸

OPPO不提供接口获取刘海尺寸,目前其有刘海屏的机型尺寸规格都是统一的。不排除以后机型会有变化。其显示屏宽度为1080px,高度为2280px。刘海区域则都是宽度为324px, 高度为80px。

2.4 小米

2.4.1 是否有刘海屏

系统增加了 property ro.miui.notch,值为1时则是 Notch 屏手机。

手头上没有小米8的手机,暂时没法验证,这里就不贴代码了,免得误导大家。后面测试过再放出来。

2.4.2 刘海尺寸

小米的状态栏高度会略高于刘海屏的高度,因此可以通过获取状态栏的高度来间接避开刘海屏,获取状态栏的高度代码如下:

public static int getStatusBarHeight(Context context) {
    int statusBarHeight = 0;
    int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
    }
    return statusBarHeight;
}

其他手机也可以通过这个方法来间接避开刘海屏,但是有可能有些手机的刘海屏高度会高于状态栏的高度,所以这个方法获取到的结果并不一定安全。

3.Android P中的刘海屏适配

3.1 Google对刘海屏的支持介绍

Android P 支持最新的全面屏以及为摄像头和扬声器预留空间的凹口屏幕。 通过全新的 DisplayCutout 类,可以确定非功能区域的位置和形状,这些区域不应显示内容。 要确定这些凹口屏幕区域是否存在及其位置,请使用 getDisplayCutout() 函数。

3.2 设置凹口屏幕显示模式

全新的窗口布局属性 layoutInDisplayCutoutMode 让您的应用可以为设备凹口屏幕周围的内容进行布局。 您可以将此属性设为下列值之一:

• LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
• LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
• LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
模式 模式说明
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 只有当DisplayCutout完全包含在系统栏中时,才允许窗口延伸到DisplayCutout区域。 否则,窗口布局不与DisplayCutout区域重叠。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 该窗口决不允许与DisplayCutout区域重叠。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。

例子:
var lp = window.attributes
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
window.attributes = lp

3.3 Android P中凹口屏幕相关接口

注意,以下接口都是要Build.VERSION.SDK_INT >= 28才能调用到。

DisplayCutout类接口
主要用于获取凹口位置和安全区域的位置等。主要接口如下所示:
方法 接口说明
getBoundingRects() 返回Rects的列表,每个Rects都是显示屏上非功能区域的边界矩形。
getSafeInsetLeft () 返回安全区域距离屏幕左边的距离,单位是px。
getSafeInsetRight () 返回安全区域距离屏幕右边的距离,单位是px。
getSafeInsetTop () 返回安全区域距离屏幕顶部的距离,单位是px。
getSafeInsetBottom() 返回安全区域距离屏幕底部的距离,单位是px。